using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using fitnesse.slim;
using java.util;

namespace FitNesse4Net
{
    public class DotNetStatementExecutor : StatementExecutorInterface
    {
        private readonly Dictionary<string, object> instances = new Dictionary<string, object>();
        private readonly List<string> paths = new List<string>();
        private readonly VariableStore variables = new VariableStore();
        private bool stopRequested;

        #region StatementExecutorInterface Members

        public void setVariable(string name, object value)
        {
            variables.setVariable(name, value);
        }

        public object addPath(string str)
        {
            paths.Add(str);
            return "OK";
        }

        public object getInstance(string instanceName)
        {
            object instance = instances[instanceName];

            if (instance != null)
            {
                return instance;
            }

            throw new SlimError(java.lang.String.format("message:<<NO_INSTANCE %s.>>", instanceName));
        }

        public object create(string instanceName, string className, object[] args)
        {
            try
            {
                instances[instanceName] = Activator.CreateInstance(GetType(className), ReplaceVariables(args));
                return "OK";
            }
            catch
            {
                stopRequested = true;
                return "ERROR";
            }
        }

        public object call(string instanceName, string methodName, params object[] args)
        {
            try
            {
                object instance = instances[instanceName];

                Type type = instance.GetType();

                MethodInfo methodInfo = type.GetMethods().FirstOrDefault(m => m.Name.ToLower() == methodName.ToLower());
                PropertyInfo propertyInfo = type.GetProperties().FirstOrDefault(p => p.Name.ToLower() == methodName.ToLower());

                object[] args2 = ReplaceVariables(args);

                if (methodInfo != null)
                {
                    return methodInfo.Invoke(instance, ConvertMethodParameters(methodInfo, args2));
                }

                if (propertyInfo != null)
                {
                    if (args.Length == 0) // get
                    {
                        MethodInfo getMethodInfo = propertyInfo.GetGetMethod();
                        return getMethodInfo.Invoke(instance, ConvertMethodParameters(getMethodInfo, args2));
                    }
                    else // set
                    {
                        MethodInfo setMethodInfo = propertyInfo.GetSetMethod();
                        return setMethodInfo.Invoke(instance, ConvertMethodParameters(setMethodInfo, args2));
                    }
                }

                throw new NotSupportedException(instanceName + " " + methodName + Arrays.toString(args).Split(' '));
            }
            catch
            {
                stopRequested = true;
                return null;
            }
        }

        public bool stopHasBeenRequested()
        {
            return stopRequested;
        }

        public void reset()
        {
            stopRequested = false;
        }

        #endregion

        private Type GetType(string className)
        {
            foreach (var assembly in FitNesse.ExternalAssemblies)
            {
                var type = assembly.GetType(className);
                if (type != null)
                {
                    return type;
                }
            }

            throw new TypeLoadException("Can't find type in external assemblies, please configure root page properly");
        }

        private object[] ConvertMethodParameters(MethodInfo method, object[] args)
        {
            var convertedArgs = new object[args.Length];
            ParameterInfo[] parameters = method.GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                MethodInfo parseMethodInfo = parameters[i].ParameterType.GetMethods().FirstOrDefault(m => m.Name == "Parse" && m.GetParameters().Length == 1);
                convertedArgs[i] = parseMethodInfo.Invoke(null, new[] {args[0]});
            }

            return convertedArgs;
        }

        private object[] ReplaceVariables(object[] args)
        {
            return variables.replaceVariables(args);
        }
    }
}